package com.kexio.user.service.impl;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kexio.auth.cache.SimpleCacheKeyUtil;
import com.kexio.auth.util.AuthContextUtils;
import com.kexio.common.enums.StatusEnum;
import com.kexio.common.exception.BusinessException;
import com.kexio.common.utils.ValidationUtils;
import com.kexio.user.entity.Menu;
import com.kexio.user.entity.Permission;
import com.kexio.user.entity.Role;
import com.kexio.user.mapper.MenuMapper;
import com.kexio.user.mapper.PermissionMapper;
import com.kexio.user.mapper.RoleMapper;
import com.kexio.user.mapper.RoleMenuMapper;
import com.kexio.user.mapper.UserRoleMapper;
import com.kexio.user.service.MenuService;

/**
 * 菜单服务实现类
 * 
 * 提供菜单管理的核心业务逻辑实现
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
@Service
public class MenuServiceImpl implements MenuService {
    
    private static final Logger logger = LoggerFactory.getLogger(MenuServiceImpl.class);
    
    @Autowired
    private MenuMapper menuMapper;
    
    // 本地缓存层已废弃（第2步架构）
    
    
    @Autowired
    private RoleMenuMapper roleMenuMapper;
    
    @Autowired
    private RoleMapper roleMapper;

    @Autowired(required = false)
    private UserRoleMapper userRoleMapper;

    // 避免循环依赖：不直接注入 UserService；必要数据通过 Mapper 读取
    
    @Autowired
    private PermissionMapper permissionMapper;
    
    @Autowired
    private SimpleCacheKeyUtil simpleCacheKeyUtil;
    
    @Autowired(required = false)
    private com.kexio.user.service.PermissionVersionService permVersionService;
    
    // ==================== 基础CRUD操作 ====================
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = "menuTree", key = "@simpleCacheKeyUtil.generateClearPattern('menu')", beforeInvocation = false)
    public Menu createMenu(Menu menu) {
        ValidationUtils.notNull(menu, "菜单信息不能为空");

        String tenantId = menu.getTenantId();
        String menuName = menu.getName();

        ValidationUtils.notBlank(menuName, "菜单名称不能为空");

        // ✅ 修复：移除外层 try-catch，避免重复日志，全局异常处理器会统一记录异常
        
        // 检查同级菜单名称唯一性
        String parentId = null;
        if (StringUtils.hasText(menu.getParentId())) {
            try {
                parentId = menu.getParentId();
            } catch (NumberFormatException e) {
                logger.warn("父菜单ID格式无效: {}", menu.getParentId());
            }
        }
        if (existsByName(menuName, parentId, null)) {
            throw BusinessException.dataExists("同级菜单名称已存在: " + menuName);
        }

        // 设置创建信息
        menu.setId(UUID.randomUUID().toString());
        menu.setCreateTime(LocalDateTime.now());
        menu.setUpdateTime(LocalDateTime.now());
        
        // 设置默认值
        if (menu.getStatus() == null) {
            menu.setStatus(StatusEnum.ENABLED.getValue()); // 默认启用
        }
        if (menu.getVisible() == null) {
            menu.setVisible(Boolean.TRUE); // 默认可见
        }
        // 逻辑删除由 MP 统一处理，无需显式设置
        if (menu.getOrderNo() == null) {
            menu.setOrderNo(getNextSortOrder(parentId));
        }

        // 保存菜单
        int result = menuMapper.insert(menu);
        if (result > 0) {
            logger.info("创建菜单成功: menuId={}, menuName={}", menu.getId(), menuName);
            
            // 菜单创建时通知相关用户权限版本更新
            // ✓ 保留内部 catch：版本更新失败不影响主流程
            if (permVersionService != null) {
                try {
                    int affectedUsers = permVersionService.onMenuChanged(menu.getId());
                    logger.info("菜单创建，批量更新用户权限版本: menuId={}, affectedUsers={}", menu.getId(), affectedUsers);
                } catch (Exception e) {
                    logger.warn("批量更新用户权限版本失败: menuId={}, error={}", menu.getId(), e.getMessage());
                }
            }
            
            return menu;
        } else {
            throw BusinessException.operationFailed("创建菜单失败");
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = "menuTree", key = "@simpleCacheKeyUtil.generateClearPattern('menu')", beforeInvocation = false)
    public Menu updateMenu(String menuId, Menu menu) {
        ValidationUtils.notNull(menuId, "菜单ID不能为空");
        ValidationUtils.notNull(menu, "菜单信息不能为空");

        String tenantId = menu.getTenantId();

        // ✅ 修复：移除外层 try-catch，避免重复日志，全局异常处理器会统一记录异常
        
        // 查询现有菜单
        Menu existingMenu = getMenuById(menuId);
        if (existingMenu == null) {
            throw BusinessException.resourceNotFound("菜单不存在");
        }

        // 检查同级菜单名称唯一性
        String menuName = menu.getName();
        if (StringUtils.hasText(menuName)) {
            String parentId = null;
            if (StringUtils.hasText(menu.getParentId())) {
                try {
                    parentId = menu.getParentId();
                } catch (NumberFormatException e) {
                    logger.warn("父菜单ID格式无效: {}", menu.getParentId());
                }
            }
            if (existsByName(menuName, parentId, menuId)) {
                throw BusinessException.dataExists("同级菜单名称已存在: " + menuName);
            }
        }

        // 检查循环引用
        if (StringUtils.hasText(menu.getParentId())) {
            try {
                String parentId = menu.getParentId();
                if (wouldCreateCircularReference(menuId, parentId)) {
                    throw BusinessException.invalidParam("菜单父级关系会形成循环引用");
                }
            } catch (NumberFormatException e) {
                logger.warn("父菜单ID格式无效: {}", menu.getParentId());
            }
        }

        // 更新菜单信息
        menu.setId(menuId);
        menu.setUpdateTime(LocalDateTime.now());

        int result = menuMapper.updateById(menu);
        if (result > 0) {
            logger.info("更新菜单成功: menuId={}", menuId);
            // 缓存已通过@CacheEvict自动清理（租户隔离安全）
            
            // 新增：菜单更新时通知相关用户权限版本更新
            // ✓ 保留内部 catch：版本更新失败不影响主流程
            if (permVersionService != null) {
                try {
                    int affectedUsers = permVersionService.onMenuChanged(menuId);
                    logger.info("菜单更新，批量更新用户权限版本: menuId={}, affectedUsers={}", menuId, affectedUsers);
                } catch (Exception e) {
                    logger.warn("批量更新用户权限版本失败: menuId={}, error={}", menuId, e.getMessage());
                }
            }
            
            return getMenuById(menuId);
        } else {
            throw BusinessException.operationFailed("更新菜单失败");
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = "menuTree", key = "@simpleCacheKeyUtil.generateClearPattern('menu')", beforeInvocation = false)
    public boolean deleteMenu(String menuId) {
        ValidationUtils.notNull(menuId, "菜单ID不能为空");

        // ✅ 修复：移除外层 try-catch，避免重复日志，全局异常处理器会统一记录异常
        
        // 查询菜单是否存在
        Menu existingMenu = getMenuById(menuId);
        if (existingMenu == null) {
            logger.warn("要删除的菜单不存在: menuId={}", menuId);
            return true; // 菜单不存在视为删除成功
        }

        // 检查是否有子菜单
        if (hasChildMenus(menuId)) {
            throw BusinessException.operationFailed("菜单存在子菜单，无法删除");
        }

        // 逻辑删除菜单，使用 @TableLogic
        int result = menuMapper.deleteById(menuId);
        if (result > 0) {
            logger.info("删除菜单成功: menuId={}", menuId);
            // 缓存已通过@CacheEvict自动清理（租户隔离安全）
            
            // 新增：菜单删除时通知相关用户权限版本更新
            // ✓ 保留内部 catch：版本更新失败不影响主流程
            if (permVersionService != null) {
                try {
                    int affectedUsers = permVersionService.onMenuChanged(menuId);
                    logger.info("菜单删除，批量更新用户权限版本: menuId={}, affectedUsers={}", menuId, affectedUsers);
                } catch (Exception e) {
                    logger.warn("批量更新用户权限版本失败: menuId={}, error={}", menuId, e.getMessage());
                }
            }
            
            return true;
        } else {
            throw BusinessException.operationFailed("删除菜单失败");
        }
    }
    
    @Override
    public Menu getMenuById(String menuId) {
        if (menuId == null) {
            return null;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getId, menuId);

        return menuMapper.selectOne(queryWrapper);
    }
    
    @Override
    public Menu getMenuByCode(String menuCode) {
        if (!StringUtils.hasText(menuCode)) {
            return null;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getName, menuCode); // 使用name作为code

        return menuMapper.selectOne(queryWrapper);
    }
    
    // ==================== 菜单列表查询 ====================
    
    @Override
    public Page<Menu> getMenuPage(Long current, Long size) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        Page<Menu> page = new Page<>(
            com.kexio.common.page.PageUtils.normalizePageNo(current),
            com.kexio.common.page.PageUtils.normalizePageSize(size)
        );
        
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                    .orderByAsc(Menu::getOrderNo)
                    .orderByDesc(Menu::getCreateTime);
        
        return menuMapper.selectPage(page, queryWrapper);
    }
    
    @Override
    public Page<Menu> getMenuPage(Long current, Long size, String menuType, String menuName, Integer status) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        Page<Menu> page = new Page<>(
            com.kexio.common.page.PageUtils.normalizePageNo(current),
            com.kexio.common.page.PageUtils.normalizePageSize(size)
        );
        
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        
        // 添加条件查询
        if (StringUtils.hasText(menuType)) {
            queryWrapper.eq(Menu::getMenuType, menuType);
        }
        if (StringUtils.hasText(menuName)) {
            queryWrapper.like(Menu::getName, menuName);
        }
        if (status != null) {
            queryWrapper.eq(Menu::getStatus, status);
        }
        
        queryWrapper.orderByAsc(Menu::getOrderNo)
                    .orderByDesc(Menu::getCreateTime);
        
        Page<Menu> menuPage = menuMapper.selectPage(page, queryWrapper);
        
        logger.debug("带条件分页查询菜单成功: current={}, size={}, menuType={}, menuName={}, status={}, total={}", 
                    current, size, menuType, menuName, status, menuPage.getTotal());
        
        return menuPage;
    }
    
    @Override
    public List<Menu> getMenusByTenant(String tenantId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                    .orderByAsc(Menu::getOrderNo)
                    .orderByDesc(Menu::getCreateTime);
        
        return menuMapper.selectList(queryWrapper);
    }
    
    @Override
    public List<Menu> getMenusByType(String menuType, String tenantId) {
        if (!StringUtils.hasText(menuType)) {
            return Collections.emptyList();
        }
        
        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                    .eq(Menu::getMenuType, menuType)
                    .orderByAsc(Menu::getOrderNo)
                    .orderByDesc(Menu::getCreateTime);
        
        return menuMapper.selectList(queryWrapper);
    }
    
    @Override
    public List<Menu> getVisibleMenus(String tenantId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                    .eq(Menu::getStatus, 1)
                    .eq(Menu::getVisible, Boolean.TRUE)
                    .orderByAsc(Menu::getOrderNo)
                    .orderByDesc(Menu::getCreateTime);
        
        return menuMapper.selectList(queryWrapper);
    }
    
    // ==================== 实现的核心方法 ====================
    
    @Override
    public Integer getNextSortOrder(String parentMenuId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                    .orderByDesc(Menu::getOrderNo)
                    .last("LIMIT 1");

        if (parentMenuId != null) {
            queryWrapper.eq(Menu::getParentId, parentMenuId);
        } else {
            queryWrapper.isNull(Menu::getParentId);
        }

        List<Menu> menus = menuMapper.selectList(queryWrapper);
        if (menus != null && !menus.isEmpty()) {
            Menu lastMenu = menus.get(0);
            Integer lastOrderNo = lastMenu.getOrderNo();
            return (lastOrderNo != null ? lastOrderNo : 0) + 1;
        }

        return 1; // 如果没有同级菜单，返回1
    }
    
    @Override
    public boolean existsByCode(String menuCode, String excludeMenuId) {
        return existsByName(menuCode, null, excludeMenuId);
    }
    
    @Override
    public boolean existsByName(String menuName, String parentMenuId, String excludeMenuId) {
        if (!StringUtils.hasText(menuName)) {
            return false;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getName, menuName);

        if (parentMenuId != null) {
            queryWrapper.eq(Menu::getParentId, parentMenuId);
        } else {
            queryWrapper.isNull(Menu::getParentId);
        }

        if (excludeMenuId != null) {
            queryWrapper.ne(Menu::getId, excludeMenuId);
        }

        return menuMapper.selectCount(queryWrapper) > 0;
    }
    
    @Override
    public boolean isMenuPathUnique(String menuPath, String tenantId, String excludeMenuId) {
        if (!StringUtils.hasText(menuPath)) {
            return true;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getPath, menuPath);

        if (excludeMenuId != null) {
            queryWrapper.ne(Menu::getId, excludeMenuId);
        }

        return menuMapper.selectCount(queryWrapper) == 0;
    }
    
    @Override
    public boolean hasChildMenus(String menuId) {
        if (menuId == null) {
            return false;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Menu::getParentId, menuId);

        return menuMapper.selectCount(queryWrapper) > 0;
    }
    
    @Override
    public boolean wouldCreateCircularReference(String menuId, String newParentMenuId) {
        if (menuId == null || newParentMenuId == null) {
            return false;
        }

        if (menuId.equals(newParentMenuId)) {
            return true; // 不能设置自己为父级
        }

        // ✅ 修复：移除外层 try-catch，让异常自然抛出
        // 检查新的父菜单及其所有祖先菜单中是否包含当前菜单
        String currentParentId = newParentMenuId;
        int maxDepth = 10; // 防止无限循环，设置最大深度
        int depth = 0;

        while (currentParentId != null && depth < maxDepth) {
            if (currentParentId.equals(menuId)) {
                return true; // 发现循环引用
            }

            Menu parentMenu = getMenuById(currentParentId);
            if (parentMenu == null || !StringUtils.hasText(parentMenu.getParentId())) {
                break;
            }

            try {
                currentParentId = parentMenu.getParentId();
            } catch (NumberFormatException e) {
                logger.warn("父菜单ID格式无效: {}", parentMenu.getParentId());
                break;
            }
            depth++;
        }

        return false;
    }
    

    @Override
    public void notifyMenuChange(String menuId, String operatorId) {
        if (!StringUtils.hasText(menuId)) {
            return;
        }

        // ✅ 修复：移除 try-catch，让异常自然抛出
        logger.info("菜单变更通知: menuId={}, operatorId={}, tenantId={}", 
                   menuId, operatorId, AuthContextUtils.getCurrentTenantId());
        
        // 刷新菜单缓存（通过@CacheEvict自动处理）
        refreshMenuCache();
    }
    
    @Override
    @CacheEvict(cacheNames = "menuTree", key = "@simpleCacheKeyUtil.generateClearPattern('menu')")
    public void refreshMenuCache() {
        // 通过@CacheEvict自动清理当前租户的菜单缓存
        logger.debug("菜单缓存已刷新: tenantId={}", AuthContextUtils.getCurrentTenantId());
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMenuStatus(String menuId, Integer status, String tenantId) {
        ValidationUtils.notNull(menuId, "菜单ID不能为空");
        ValidationUtils.notNull(status, "状态不能为空");
        ValidationUtils.notBlank(tenantId, "租户ID不能为空");

        // ✅ 修复：移除 try-catch，避免重复日志，全局异常处理器会统一记录异常
        
        // 验证状态值
        ValidationUtils.isTrue(status == 0 || status == 1, "菜单状态只能为0（禁用）或1（启用）");

        // 检查菜单是否存在
        Menu existingMenu = getMenuById(menuId);
        if (existingMenu == null) {
            throw BusinessException.resourceNotFound("菜单不存在");
        }

        // 更新状态
        Menu updateMenu = new Menu();
        updateMenu.setId(menuId);
        updateMenu.setStatus(status);
        updateMenu.setUpdateTime(LocalDateTime.now());

        int result = menuMapper.updateById(updateMenu);
        if (result > 0) {
            logger.info("更新菜单状态成功: menuId={}, status={}={}", menuId, status);
            
            // 通知菜单变更
            notifyMenuChange(menuId, null);
            
            return true;
        } else {
            throw BusinessException.operationFailed("更新菜单状态失败");
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMenuVisible(String menuId, Boolean visible, String tenantId) {
        ValidationUtils.notNull(menuId, "菜单ID不能为空");
        ValidationUtils.notNull(visible, "可见性不能为空");
        ValidationUtils.notBlank(tenantId, "租户ID不能为空");

        // ✅ 修复：移除 try-catch，避免重复日志，全局异常处理器会统一记录异常
        
        // 检查菜单是否存在
        Menu existingMenu = getMenuById(menuId);
        if (existingMenu == null) {
            throw BusinessException.resourceNotFound("菜单不存在");
        }

        // 更新可见性
        Menu updateMenu = new Menu();
        updateMenu.setId(menuId);
        updateMenu.setVisible(visible);
        updateMenu.setUpdateTime(LocalDateTime.now());

        int result = menuMapper.updateById(updateMenu);
        if (result > 0) {
            logger.info("更新菜单可见性成功: menuId={}, visible={}={}", menuId, visible);
            
            // 通知菜单变更
            notifyMenuChange(menuId, null);
            
            return true;
        } else {
            throw BusinessException.operationFailed("更新菜单可见性失败");
        }
    }
    
    // ==================== 核心查询方法 ====================
    
    @Override
    public List<Menu> getMenuTree(String tenantId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        // 查询所有菜单并构建树形结构
        LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
        query
             .eq(Menu::getStatus, StatusEnum.ENABLED.getValue())
             .orderByAsc(Menu::getOrderNo, Menu::getId);
        
        List<Menu> allMenus = menuMapper.selectList(query);
        if (allMenus.isEmpty()) {
            logger.debug("租户无可用菜单: tenantId={}");
            return Collections.emptyList();
        }
        
        // 构建树形结构
        List<Menu> treeMenus = buildMenuTree(allMenus, null);
        logger.debug("构建菜单树成功: tenantId={}, totalMenus={}, rootMenus={}", 
                    tenantId, allMenus.size(), treeMenus.size());
        
        return treeMenus;
    }
    
    @Override
    public List<Menu> getChildMenus(String parentMenuId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
        query.eq(Menu::getParentId, parentMenuId)
             .eq(Menu::getStatus, StatusEnum.ENABLED.getValue())
             .orderByAsc(Menu::getOrderNo, Menu::getId);
        
        List<Menu> childMenus = menuMapper.selectList(query);
        logger.debug("获取子菜单成功: parentMenuId={}={}, count={}", 
                    parentMenuId, childMenus.size());
        
        return childMenus;
    }
    
    @Override
    public List<Menu> getAncestorMenus(String menuId, String tenantId) {
        // ✅ 修复：移除外层 try-catch，让异常自然抛出
        List<Menu> ancestors = new ArrayList<>();
        String currentMenuId = menuId;
        
        // 递归查找祖先菜单
        while (currentMenuId != null) {
            Menu currentMenu = getMenuById(currentMenuId);
            if (currentMenu == null) {
                break;
            }
            
            ancestors.add(0, currentMenu); // 添加到列表开头，保持层级顺序
            String parentId = currentMenu.getParentId();
            if (!StringUtils.hasText(parentId)) {
                currentMenuId = null;
            } else {
                try {
                    currentMenuId = parentId;
                } catch (NumberFormatException ex) {
                    logger.warn("父菜单ID格式无效: {}", parentId);
                    currentMenuId = null;
                }
            }
        }
        
        logger.debug("获取祖先菜单成功: menuId={}={}, ancestorCount={}", 
                    menuId, ancestors.size());
        
        return ancestors;
    }
    
    @Override
    public List<Menu> getDescendantMenus(String menuId, String tenantId) {
        // ✅ 修复：移除 try-catch，让异常自然抛出
        List<Menu> descendants = new ArrayList<>();
        collectDescendantMenus(menuId, descendants);
        
        logger.debug("获取后代菜单成功: menuId={}={}, descendantCount={}", 
                    menuId, descendants.size());
        
        return descendants;
    }
    
    @Override
    public List<Menu> getRootMenus(String tenantId) {
        LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
        query
             .isNull(Menu::getParentId)  // 父菜单ID为空的是根菜单
             .eq(Menu::getStatus, StatusEnum.ENABLED.getValue())
             .orderByAsc(Menu::getOrderNo, Menu::getId);
        
        List<Menu> rootMenus = menuMapper.selectList(query);
        logger.debug("获取根菜单成功: tenantId={}, count={}", rootMenus.size());
        
        return rootMenus;
    }
    
    @Override
    public List<Menu> getUserMenus(String userId) {
        logger.info("========================================");
        logger.info("【菜单Service-步骤1】开始获取用户菜单 - userId={}", userId);
        
        // 1. 获取用户的所有角色
        List<String> userRoles = getUserRoles(userId);
        logger.info("【菜单Service-步骤2】用户角色查询完成: roleCount={}, roles={}", userRoles.size(), userRoles);
        
        if (userRoles.isEmpty()) {
            logger.warn("【菜单Service-警告】用户无角色，返回空菜单 - userId={}", userId);
            logger.info("========================================");
            return Collections.emptyList();
        }
        
        // 2. 批量查询角色的菜单权限（一条SQL搞定）
        Set<String> menuIds = new HashSet<>();
        if (roleMenuMapper != null) {
            List<String> userRoleList = new ArrayList<>(userRoles);
            logger.info("【菜单Service-步骤3】准备查询角色菜单: roleCodes={}", userRoleList);
            
            List<String> roleMenuIds = roleMenuMapper.selectMenuIdsByRoleCodesAndTenant(userRoleList);
            logger.info("【菜单Service-步骤4】角色菜单ID查询完成: menuIdCount={}", roleMenuIds != null ? roleMenuIds.size() : 0);
            
            if (roleMenuIds != null && !roleMenuIds.isEmpty()) {
                logger.info("【菜单Service-步骤4.1】前5个菜单ID: {}", 
                    roleMenuIds.stream().limit(5).collect(Collectors.toList()));
                menuIds.addAll(roleMenuIds);
            }
        } else {
            logger.error("【菜单Service-错误】roleMenuMapper为null！");
        }
        
        if (menuIds.isEmpty()) {
            logger.warn("【菜单Service-警告】用户角色无菜单权限 - userId={}, roles={}", userId, userRoles);
            logger.info("========================================");
            return Collections.emptyList();
        }
        
        // 3. 查询菜单详细信息
        logger.info("【菜单Service-步骤5】准备查询菜单详情: menuIdCount={}", menuIds.size());
        LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
        List<String> idStrings = menuIds.stream().map(String::valueOf).collect(Collectors.toList());
        query
             .in(Menu::getId, idStrings)
             .eq(Menu::getStatus, StatusEnum.ENABLED.getValue())
             .orderByAsc(Menu::getOrderNo, Menu::getId);
        
        List<Menu> userMenus = menuMapper.selectList(query);
        logger.info("【菜单Service-步骤6】菜单详情查询完成: menuCount={}", userMenus != null ? userMenus.size() : 0);
        
        if (userMenus != null && !userMenus.isEmpty()) {
            logger.info("【菜单Service-步骤6.1】前3个菜单: {}", 
                userMenus.stream().limit(3)
                    .map(m -> m.getName() + "(" + m.getTitle() + ")")
                    .collect(Collectors.toList()));
        }
        
        logger.info("========================================");
        return userMenus;
    }
    
    @Override
    @Cacheable(cacheNames = "menuTree", 
               key = "@simpleCacheKeyUtil.getCurrentEffectiveTenantId() + ':menu:' + #userId + ':tree'", 
               condition = "#userId != null")
    public List<Menu> getUserMenuTree(String userId, String tenantId) {
        logger.info("========================================");
        logger.info("【getUserMenuTree-开始】userId={}, tenantId={}", userId, tenantId);
        logger.info("========================================");
        
        // 1. 获取用户的所有菜单
        List<Menu> userMenus = getUserMenus(userId);
        logger.info("【getUserMenuTree-步骤1】getUserMenus返回: menuCount={}", userMenus != null ? userMenus.size() : 0);
        
        if (userMenus == null || userMenus.isEmpty()) {
            logger.warn("【getUserMenuTree-警告】用户无菜单数据，返回空树 - userId={}", userId);
            logger.info("========================================");
            return Collections.emptyList();
        }
        
        // 2. 构建菜单树
        logger.info("【getUserMenuTree-步骤2】开始构建菜单树...");
        List<Menu> menuTree = buildMenuTree(userMenus, null);
        logger.info("【getUserMenuTree-步骤3】菜单树构建完成: rootMenus={}, totalMenus={}", 
                    menuTree != null ? menuTree.size() : 0, userMenus.size());
        
        if (menuTree != null && !menuTree.isEmpty()) {
            logger.info("【getUserMenuTree-步骤3.1】根菜单: {}", 
                menuTree.stream().limit(3)
                    .map(m -> m.getName() + "(" + (m.getChildren() != null ? m.getChildren().size() : 0) + "子菜单)")
                    .collect(Collectors.toList()));
        }
        
        logger.info("========================================");
        return menuTree;
    }
    
    @Override
    public List<Menu> getUserNavMenus(String userId, String tenantId) {
        // 1. 获取用户菜单
        List<Menu> userMenus = getUserMenus(userId);
        
        // 2. 过滤出导航菜单（菜单类型为MENU或BUTTON的父菜单）
        List<Menu> navMenus = userMenus.stream()
            .filter(menu -> "MENU".equals(menu.getMenuType()) || "DIRECTORY".equals(menu.getMenuType()))
            .filter(menu -> Boolean.TRUE.equals(menu.getVisible()))
            .collect(Collectors.toList());
        
        // 3. 构建导航菜单树
        List<Menu> navMenuTree = buildMenuTree(navMenus, null);
        logger.debug("获取用户导航菜单成功: userId={}={}, navMenus={}", 
                    userId, navMenuTree.size());
        
        return navMenuTree;
    }
    
    
    @Override
    public boolean hasMenuAccess(String userId, String menuId) {
        try {
            // 1. 验证菜单是否存在且启用
            Menu menu = getMenuById(menuId);
            if (menu == null || !StatusEnum.ENABLED.getValue().equals(menu.getStatus())) {
                logger.debug("菜单不存在或已禁用: menuId={}={}", menuId);
                return false;
            }
            
            // 2. 获取用户菜单列表
            List<Menu> userMenus = getUserMenus(userId);
            boolean hasAccess = userMenus.stream()
                .anyMatch(m -> Objects.equals(m.getId(), menuId != null ? menuId.toString() : null));
            
            logger.debug("用户菜单访问权限检查: userId={}, menuId={}={}, hasAccess={}", 
                        userId, menuId, hasAccess);
            
            return hasAccess;
        } catch (Exception e) {
            logger.error("检查用户菜单访问权限失败: userId={}, menuId={}={}, error={}", 
                        userId, menuId, e.getMessage(), e);
            return false;
        }
    }
    
    @Override
    public List<Menu> getMenusByRole(String roleId) {
        try {
            // 1. 获取角色的菜单ID列表
            List<String> roleMenuIds = getRoleMenuIds(roleId);
            if (roleMenuIds.isEmpty()) {
                logger.debug("角色无菜单权限: roleId={}={}", roleId);
                return Collections.emptyList();
            }
            
            // 2. 查询菜单详细信息
            LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
            List<String> idStrings = roleMenuIds.stream().map(String::valueOf).collect(Collectors.toList());
            query
                 .in(Menu::getId, idStrings)
                 .eq(Menu::getStatus, StatusEnum.ENABLED.getValue())
                 .orderByAsc(Menu::getOrderNo, Menu::getId);
            
            List<Menu> roleMenus = menuMapper.selectList(query);
            logger.debug("获取角色菜单成功: roleId={}={}, menuCount={}", 
                        roleId, roleMenus.size());
            
            return roleMenus;
        } catch (Exception e) {
            logger.error("获取角色菜单失败: roleId={}={}, error={}", 
                        roleId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    @Override
    public List<Menu> getRoleMenuTree(String roleId, String tenantId) {
        try {
            // 1. 获取角色的所有菜单
            List<Menu> roleMenus = getMenusByRole(roleId);
            if (roleMenus.isEmpty()) {
                return Collections.emptyList();
            }
            
            // 2. 构建菜单树
            List<Menu> menuTree = buildMenuTree(roleMenus, null);
            logger.debug("构建角色菜单树成功: roleId={}={}, rootMenus={}", 
                        roleId, menuTree.size());
            
            return menuTree;
        } catch (Exception e) {
            logger.error("构建角色菜单树失败: roleId={}={}, error={}", 
                        roleId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    @Override
    public List<Role> getRolesByMenu(String menuId, String tenantId) {
        try {
            // 真正实现：查询有该菜单权限的角色
            List<Role> roles = new ArrayList<>();
            
            if (roleMenuMapper != null) {
                // 通过RoleMenuMapper查询角色菜单关联表
                // SELECT r.* FROM sys_role_menu rm JOIN sys_role r ON rm.role_id = r.id
                // WHERE rm.menu_id = ? AND rm.tenant_id = ? AND rm.deleted = 0 AND r.deleted = 0
                List<String> roleIds = roleMenuMapper.selectRoleIdsByMenuIdAndTenant(menuId);
                
                if (!roleIds.isEmpty()) {
                    // 查询角色详细信息
                    LambdaQueryWrapper<Role> query = new LambdaQueryWrapper<>();
                    query.in(Role::getId, roleIds)
                         .eq(Role::getStatus, StatusEnum.ENABLED.getValue())
                         .orderByAsc(Role::getOrderNo, Role::getId);
                    
                    roles = roleMapper.selectList(query);
                }
            } else {
                // 降级处理：查询所有角色
                LambdaQueryWrapper<Role> query = new LambdaQueryWrapper<>();
                query
                     .eq(Role::getStatus, StatusEnum.ENABLED.getValue())
                     .orderByAsc(Role::getOrderNo, Role::getId);
                roles = roleMapper.selectList(query);
            }
            
            logger.debug("获取菜单关联角色: menuId={}={}, roleCount={}", 
                        menuId, roles.size());
            
            return roles;
        } catch (Exception e) {
            logger.error("获取菜单关联角色失败: menuId={}={}, error={}", 
                        menuId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    @Override
    public List<Permission> getMenuPermissions(String menuId, String tenantId) {
        try {
            if (!StringUtils.hasText(menuId) || !StringUtils.hasText(tenantId)) {
                logger.warn("获取菜单权限参数无效: menuId={}={}", menuId);
                return Collections.emptyList();
            }
            
            // 新架构：直接通过权限表的menu_id字段查询
            LambdaQueryWrapper<Permission> query = new LambdaQueryWrapper<>();
            query.eq(Permission::getMenuId, menuId)
                 .eq(Permission::getStatus, StatusEnum.ENABLED.getValue())
                 .eq(Permission::getDeleted, 0)
                 .orderByAsc(Permission::getOrderNo);
            
            List<Permission> permissions = permissionMapper.selectList(query);
            logger.debug("获取菜单权限: menuId={}={}, permissionCount={}", 
                        menuId, permissions.size());
            
            return permissions != null ? permissions : Collections.emptyList();
        } catch (Exception e) {
            logger.error("获取菜单权限失败: menuId={}={}, error={}", 
                        menuId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    @Override
    public List<Menu> getMenusByPermission(String permissionId, String tenantId) {
        try {
            // 真正实现：查询有指定权限的菜单
            List<Menu> menus = new ArrayList<>();
            
            // 新架构：直接通过权限表的menu_id字段查询关联菜单
            Permission permission = permissionMapper.selectById(permissionId);
            if (permission != null && StringUtils.hasText(permission.getMenuId())) {
                // 查询权限关联的菜单
                Menu menu = getMenuById(permission.getMenuId());
                if (menu != null && StatusEnum.ENABLED.getValue().equals(menu.getStatus())) {
                    menus.add(menu);
                }
            }
            
            logger.debug("获取权限关联菜单: permissionId={}={}, menuCount={}", 
                        permissionId, menus.size());
            
            return menus;
        } catch (Exception e) {
            logger.error("获取权限关联菜单失败: permissionId={}={}, error={}", 
                        permissionId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removePermissions(String menuId, List<String> permissionIds, String tenantId) {
        if (menuId == null) {
            throw new IllegalArgumentException("菜单ID不能为空");
        }
        if (permissionIds == null || permissionIds.isEmpty()) {
            throw new IllegalArgumentException("权限ID列表不能为空");
        }
        
        
        try {
            // 检查菜单是否存在
            Menu existingMenu = getMenuById(menuId);
            if (existingMenu == null) {
                throw BusinessException.resourceNotFound("菜单不存在");
            }
            
            // 新架构：将指定权限的menu_id设置为null，解除与菜单的关联
            if (permissionIds != null && !permissionIds.isEmpty()) {
                LambdaUpdateWrapper<Permission> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(Permission::getMenuId, null)
                            .set(Permission::getUpdateTime, LocalDateTime.now())
                            .set(Permission::getUpdateBy, "system") // 或使用operatorId参数
                            .in(Permission::getId, permissionIds)
                            .eq(Permission::getMenuId, menuId); // 只更新当前关联到此菜单的权限
                            
                int updated = permissionMapper.update(null, updateWrapper);
                logger.info("解除权限菜单关联: menuId={}, permissionIds={}, updated={}", 
                          menuId, permissionIds, updated);
            }
            
            // 3. 由事件驱动失效本地L1；不再直接清理缓存
            // 删除菜单权限关联表中的相应记录
            logger.info("从菜单移除权限: menuId={}, permissionIds={}={}", 
                       menuId, permissionIds);
            
            // 通知菜单变更
            notifyMenuChange(menuId, null);
            
            return true;
            
        } catch (Exception e) {
            logger.error("移除菜单权限异常: menuId={}, permissionIds={}={}, error={}", 
                        menuId, permissionIds, e.getMessage(), e);
            if (e instanceof BusinessException be) {
                throw be;
            }
            throw BusinessException.operationFailed("移除菜单权限失败: " + e.getMessage());
        }
    }
    
    
    @Override
    public List<Menu> buildBreadcrumbPath(String menuId) {
        List<Menu> path = new ArrayList<>();
        if (menuId == null) {
            return path;
        }
        try {
            String currentId = menuId;
            int guard = 0;
            while (currentId != null && guard++ < 32) {
                Menu m = getMenuById(currentId);
                if (m == null) break;
                path.add(0, m);
                String pid = m.getParentId();
                if (!StringUtils.hasText(pid)) break;
                try {
                    currentId = pid;
                } catch (NumberFormatException ex) {
                    logger.warn("父菜单ID格式无效: {}", pid);
                    break;
                }
            }
        } catch (Exception e) {
            logger.error("构建面包屑失败: menuId={}={}, error={}", menuId, e.getMessage(), e);
        }
        return path;
    }
    
    @Override
    public String getMenuFullPath(String menuId, String tenantId) {
        List<Menu> crumbs = buildBreadcrumbPath(menuId);
        if (crumbs.isEmpty()) return "/";
        List<String> parts = new ArrayList<>();
        for (Menu m : crumbs) {
            if (StringUtils.hasText(m.getPath())) {
                parts.add(m.getPath());
            } else if (StringUtils.hasText(m.getName())) {
                parts.add(m.getName());
            }
        }
        String full = "/" + String.join("/", parts);
        return full.replaceAll("/+", "/");
    }
    
    @Override
    public Menu getMenuByPath(String menuPath, String tenantId) {
        if (!StringUtils.hasText(menuPath)) {
            return null;
        }
        try {
            LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
            query.eq(Menu::getPath, menuPath)
;
            return menuMapper.selectOne(query);
        } catch (Exception e) {
            logger.error("根据路径查询菜单失败: path={}={}, error={}", menuPath, e.getMessage(), e);
            return null;
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMenuSort(String menuId, Integer newSortOrder, String tenantId, String operatorId) {
        if (menuId == null) {
            throw new IllegalArgumentException("菜单ID不能为空");
        }
        if (newSortOrder == null) {
            throw new IllegalArgumentException("新排序值不能为空");
        }
        
        
        try {
            // 检查菜单是否存在
            Menu existingMenu = getMenuById(menuId);
            if (existingMenu == null) {
                throw BusinessException.resourceNotFound("菜单不存在");
            }
            
            // 更新菜单排序
            LambdaUpdateWrapper<Menu> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(Menu::getId, menuId)
                        .set(Menu::getOrderNo, newSortOrder)
                        .set(Menu::getUpdateTime, LocalDateTime.now());
            
            boolean result = menuMapper.update(null, updateWrapper) > 0;
            
            if (result) {
                logger.info("菜单排序更新成功: menuId={}, newSortOrder={}={}", 
                           menuId, newSortOrder);
                
                // 通知菜单变更
                notifyMenuChange(menuId, operatorId);
                
                return true;
            } else {
                throw BusinessException.operationFailed("更新菜单排序失败");
            }
            
        } catch (Exception e) {
            logger.error("更新菜单排序异常: menuId={}, newSortOrder={}={}, error={}", 
                        menuId, newSortOrder, e.getMessage(), e);
            if (e instanceof BusinessException be) { throw be; }
            throw BusinessException.operationFailed("更新菜单排序失败: " + e.getMessage());
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateMenuSort(String parentMenuId, Map<String, Integer> menuIdSortMap, String tenantId, String operatorId) {
        if (menuIdSortMap == null || menuIdSortMap.isEmpty()) {
            throw new IllegalArgumentException("菜单排序映射不能为空");
        }
        
        try {
            // 批量查询所有待更新的菜单（一条SQL搞定）
            List<String> menuIds = new ArrayList<>(menuIdSortMap.keySet());
            LambdaQueryWrapper<Menu> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(Menu::getId, menuIds);
            List<Menu> menus = menuMapper.selectList(queryWrapper);
            
            // 过滤出有效的菜单ID（存在且属于指定父菜单）
            Map<String, Integer> validMenuSortMap = new java.util.HashMap<>();
            for (Menu menu : menus) {
                if (parentMenuId != null && !parentMenuId.equals(menu.getParentId())) {
                    logger.warn("菜单不属于指定父菜单，跳过: menuId={}, expectedParentId={}, actualParentId={}", 
                               menu.getId(), parentMenuId, menu.getParentId());
                    continue;
                }
                
                // 只保留有效的菜单
                String menuId = menu.getId();
                if (menuIdSortMap.containsKey(menuId)) {
                    validMenuSortMap.put(menuId, menuIdSortMap.get(menuId));
                }
            }
            
            if (validMenuSortMap.isEmpty()) {
                logger.warn("没有有效的菜单需要更新排序: parentMenuId={}", parentMenuId);
                return false;
            }
            
            // 批量更新菜单排序（一条SQL搞定）
            int updatedCount = menuMapper.batchUpdateMenuSort(validMenuSortMap);
            
            if (updatedCount > 0) {
                logger.info("批量更新菜单排序成功: parentMenuId={}, updatedCount={}", 
                           parentMenuId, updatedCount);
                
                // 通知菜单变更
                notifyMenuChange(parentMenuId, operatorId);
                
                return true;
            } else {
                logger.warn("没有菜单排序被更新: parentMenuId={}", parentMenuId);
                return false;
            }
            
        } catch (Exception e) {
            logger.error("批量更新菜单排序异常: parentMenuId={}={}, error={}", 
                        parentMenuId, e.getMessage(), e);
            if (e instanceof BusinessException be) { throw be; }
            throw BusinessException.operationFailed("批量更新菜单排序失败: " + e.getMessage());
        }
    }
    
    @Override
    public Long countMenusByTenant(String tenantId) {
        return menuMapper.countMenusByTenant(tenantId);
    }
    
    @Override
    public Long countActiveMenus(String tenantId) {
        return menuMapper.countActiveMenus(tenantId);
    }
    
    @Override
    public Map<String, Long> countMenusByType(String tenantId) {
        List<Map<String, Object>> rows = menuMapper.countMenusByType(tenantId);
        Map<String, Long> result = new HashMap<>();
        if (rows != null) {
            for (java.util.Map<String, Object> r : rows) {
                Object type = r.get("menu_type");
                Object cnt = r.get("count");
                if (type != null && cnt != null) {
                    result.put(String.valueOf(type), Long.valueOf(String.valueOf(cnt)));
                }
            }
        }
        return result;
    }
    
    @Override
    public Long countChildMenus(String parentMenuId, String tenantId) {
        if (parentMenuId == null) {
            return 0L;
        }
        LambdaQueryWrapper<Menu> query = new LambdaQueryWrapper<>();
        query
             .eq(Menu::getParentId, parentMenuId);
        return menuMapper.selectCount(query);
    }
    
    @Override
    public void refreshUserMenuCache(String userId, String tenantId) {
        logger.debug("刷新用户菜单缓存: userId={}={}", userId);
        
        if (!StringUtils.hasText(userId) || !StringUtils.hasText(tenantId)) {
            logger.warn("用户ID或租户ID为空，跳过缓存刷新: userId={}={}", userId);
            return;
        }
        
        try {
            // 由事件驱动统一失效各实例L1；此处不执行本地或Redis直接清理
            
            logger.info("用户菜单缓存刷新完成: userId={}={}", userId);
            
        } catch (Exception e) {
            logger.error("刷新用户菜单缓存失败: userId={}={}, error={}", userId, e.getMessage(), e);
        }
    }
    
    @Override
    public List<Menu> getUserVisibleMenus(String userId, String tenantId) {
        try {
            List<Menu> menus = getUserMenus(userId);
            return menus.stream()
                    .filter(m -> m != null && Integer.valueOf(1).equals(m.getStatus()) && Boolean.TRUE.equals(m.getVisible()))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("获取用户可见菜单失败: userId={}, error={}", userId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    // ==================== 私有辅助方法 ====================
    
    /**
     * 获取用户角色列表
     */
    private List<String> getUserRoles(String userId) {
        try {
            logger.info("【getUserRoles】开始查询用户角色: userId={}", userId);
            
            // 真实实现：从用户角色关联查询用户角色编码
            List<String> roleCodes = new ArrayList<>();
            
            if (roleMapper != null) {
                logger.info("【getUserRoles】roleMapper可用，执行SQL查询...");
                roleCodes = roleMapper.selectUserRoleCodes(userId);
                logger.info("【getUserRoles】SQL查询完成: roleCount={}, roles={}", 
                    roleCodes != null ? roleCodes.size() : 0, roleCodes);
            } else {
                logger.error("【getUserRoles】roleMapper为null！无法查询角色！");
            }
            
            return roleCodes != null ? roleCodes : Collections.emptyList();
        } catch (Exception e) {
            logger.error("【getUserRoles】查询失败: userId={}, error={}", userId, e.getMessage(), e);
            return Collections.emptyList();
        }
    }
    
    /**
     * 获取角色菜单ID列表
     */
    private List<String> getRoleMenuIds(String roleCode) {
        try {
            if (roleMenuMapper != null) {
                List<String> ids = roleMenuMapper.selectMenuIdsByRoleCodeAndTenant(roleCode);
                logger.debug("获取角色菜单: roleCode={}={}, menuIds={}", roleCode, ids);
                return ids != null ? ids : Collections.emptyList();
            }
            return Collections.emptyList();
        } catch (Exception e) {
            logger.error("获取角色菜单失败: roleCode={}={}, error={}", roleCode, e.getMessage());
            return Collections.emptyList();
        }
    }
    
    // 已删除废弃的 getRoleMenuPermissions 方法
    // 原因：在新架构下已简化，直接从角色权限和权限表的menu_id字段获取
    
    /**
     * 构建菜单树形结构（字符串ID版，带循环检测）
     */
    private List<Menu> buildMenuTree(List<Menu> allMenus, Long parentId) {
        String parentIdStr = parentId != null ? String.valueOf(parentId) : null;
        return buildMenuTree(allMenus, parentIdStr, new HashSet<>());
    }

    private List<Menu> buildMenuTree(List<Menu> allMenus, String parentId, Set<String> pathVisited) {
        List<Menu> children = new ArrayList<>();
        for (Menu menu : allMenus) {
            if (Objects.equals(menu.getParentId(), parentId)) {
                String currentId = menu.getId();
                if (currentId != null && pathVisited.contains(currentId)) {
                    logger.warn("检测到菜单循环引用，跳过该分支: id={}", currentId);
                    continue;
                }
                if (currentId != null) {
                    pathVisited.add(currentId);
                }
                List<Menu> childList = buildMenuTree(allMenus, currentId, pathVisited);
                if (currentId != null) {
                    pathVisited.remove(currentId);
                }
                if (menu.getChildren() == null) {
                    menu.setChildren(new ArrayList<>());
                }
                menu.getChildren().addAll(childList);
                children.add(menu);
            }
        }
        return children;
    }
    
    /**
     * 递归收集后代菜单
     */
    private void collectDescendantMenus(String parentMenuId, List<Menu> descendants) {
        List<Menu> childMenus = getChildMenus(parentMenuId);
        for (Menu child : childMenus) {
            descendants.add(child);
            String childId = child.getId();
            if (StringUtils.hasText(childId)) {
                try {
                    String id = childId;
                    collectDescendantMenus(id, descendants);
                } catch (NumberFormatException ex) {
                    logger.warn("子菜单ID格式无效: {}", childId);
                }
            }
        }
    }
    
    // 已删除废弃的 syncMenuPermissionsInternal 方法
    // 原因：在新架构下已简化，直接更新权限表的menu_id字段
    // 替代方案：直接使用PermissionService更新Permission的menu_id字段
}